package cn.iocoder.yudao.module.infra.framework.log;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.module.infra.framework.log.config.HttpTraceConfig;
import cn.iocoder.yudao.module.infra.util.TraceIdUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Map;

import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;

@Slf4j
@RequiredArgsConstructor
public class HttpTraceIdInterceptor implements HandlerInterceptor {
    private static final String HEADER_TRACE_ID = "TraceId";
    /**
     * HTTP 请求时，访问令牌的请求 Header
     */
    private String tokenHeader = "Authorization";
    /**
     * HTTP 请求时，访问令牌的请求参数
     *
     * 初始目的：解决 WebSocket 无法通过 header 传参，只能通过 token 参数拼接
     */
    private String tokenParameter = "token";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        try {
            // 从请求头中获取TID
            String tid = request.getHeader(HEADER_TRACE_ID);
            if (StrUtil.isEmpty(tid)) {
                tid = IdUtil.fastSimpleUUID();
            }

            // 将TID存储到MDC中，使其在整个请求过程中可用
            TraceIdUtil.setTraceId(tid);

            // 设置请求开始执行时间
            request.setAttribute("startTime", System.currentTimeMillis());

            final Long userId = WebFrameworkUtils.getLoginUserId(request);
            final Map<String, String> queryString = ServletUtils.getParamMap(request);
            final String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : null;

            final String token = SecurityFrameworkUtils.obtainAuthorization(request, tokenHeader, tokenParameter);

            log.info("http请求-开始, method = {}, uri = {}, token = {}, userId = {}, queryString = {}, requestBody = {}",
                    request.getMethod(), request.getRequestURI(), token, userId,
                    JsonUtils.toJsonString(queryString), JsonUtils.toJsonString(requestBody));
        } catch (Exception e) {
            log.error("日志-异常", e);
        }

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        try {
            final CommonResult<?> result = WebFrameworkUtils.getCommonResult(request);

            final long startTime = Long.parseLong(request.getAttribute("startTime").toString());
            final LocalDateTime endTime = LocalDateTimeUtil.now();
            final long duration = LocalDateTimeUtil.between(LocalDateTimeUtil.of(startTime), endTime, ChronoUnit.MILLIS);
            final int statusCode = response.getStatus();

            if (result != null) {
                final boolean disableResponseBody = isDisableResponseBody(request);
                if (disableResponseBody) {
                    if (result.isSuccess()) {
                        log.info("http请求-成功, duration = {}Ms, statusCode = {}", duration, statusCode);
                    } else {
                        log.warn("http请求-失败, duration = {}Ms, statusCode = {}, resultCode = {}, resultMsg = {}, resultData = {}",
                                duration, statusCode, result.getCode(), result.getMsg(), toJsonString(result.getData()));
                    }
                } else {
                    if (result.isSuccess()) {
                        log.info("http请求-成功, duration = {}Ms, statusCode = {}, resultData = {}",
                                duration, statusCode, toJsonString(result.getData()));
                    } else {
                        log.warn("http请求-失败, duration = {}Ms, statusCode = {}, resultCode = {}, resultMsg = {}, resultData = {}",
                                duration, statusCode, result.getCode(), result.getMsg(), toJsonString(result.getData()));
                    }
                }
            } else {
                if (ex != null) {
                    log.warn(String.format("http请求-异常, duration = %sMs, statusCode = %s", duration, statusCode), ex);
                } else {
                    log.info("http请求-成功, duration = {}Ms, statusCode = {}", duration, statusCode);
                }
            }
        } catch (Exception e) {
            log.error("日志-异常", e);
        } finally {
            // 清除MDC中的TID，以免影响其他请求
            TraceIdUtil.removeTraceId();
        }
    }

    private boolean isDisableResponseBody(HttpServletRequest request) {
        final String requestURI = request.getRequestURI();
        final HttpTraceConfig httpTraceConfig = HttpTraceConfig.getHttpTraceConfig();

        final List<String> disableResponseBodyUrls = httpTraceConfig.getDisableResponseBodyUrls();
        if (CollUtil.isNotEmpty(disableResponseBodyUrls)) {
            for (String urlPatter : disableResponseBodyUrls) {
                final boolean match = ReUtil.isMatch(urlPatter, requestURI);
                if (match) {
                    return true;
                }
            }
        }
        return false;
    }


}
